#pragma once

#include "Containers/Optional.h"
#include "Containers/Delegate.h"
#include "Containers/WeakObjectPtr.h"


class CFunction;

class TWidgetAttributeValueGetterBase
{
public:
    virtual ~TWidgetAttributeValueGetterBase() = default;

    //get value of this attribute
    //must use IsValid() to check if this getter is valid
    //@OutValue , must allocate memory before call this function
    virtual void GetValuePointer(void* OutValue) = 0;

    virtual bool IsValid() { return true; }
};


template <typename T>
class TWidgetAttributeConstantValueGetter : public TWidgetAttributeValueGetterBase
{
    T Value;
public:
    TWidgetAttributeConstantValueGetter(const T& InValue)
        :Value(InValue)
    {
    }

    TWidgetAttributeConstantValueGetter(T&& InValue)
        :Value(InValue)
    {
    }

    virtual void GetValuePointer(void*  OutValue) override
    {
        *reinterpret_cast<T*>(OutValue) = Value;
    }

};

template <typename T>
class TWidgetAttributeFunctionValueGetter : public TWidgetAttributeValueGetterBase
{
    std::shared_ptr<TFunction<T>> Function;
public:
    TWidgetAttributeFunctionValueGetter(std::shared_ptr<TFunction<T>> InFunction)
        :Function(InFunction)
    {
    }

    virtual void GetValuePointer(void*  OutValue) override 
    {
        *reinterpret_cast<T*>(OutValue) = Function->ExecuteImpl();
    }

};

template<typename T>
class TWidgetAttribute
{
protected:
    std::shared_ptr<TWidgetAttributeValueGetterBase> Getter;

public:  
    TWidgetAttribute()
    {}

    TWidgetAttribute(const T& InValue)
    {
        Getter=std::make_shared<TWidgetAttributeConstantValueGetter<T>>(InValue);
    }

    TWidgetAttribute(T&& InValue)
    {
        Getter=std::make_shared<TWidgetAttributeConstantValueGetter<T>>(std::forward<T>(InValue));
    }

    TWidgetAttribute(const TDelegate<T>& InFunction)
    {
        Getter=std::make_shared<TWidgetAttributeFunctionValueGetter<T>>(InFunction.Function);
    }

    TWidgetAttribute(std::function<T()> InFunction)
    {
        TFunction<T> * RawFunction = new SSTDFunction<T>(InFunction);

        Getter=std::make_shared<TWidgetAttributeFunctionValueGetter<T>>(
            std::shared_ptr<TFunction<T>>(RawFunction)
        );
    }


    //example for construct from a CFunciton* :
    // auto Getter = std::make_shared<CWidgetAttributeScriptFunctionValueGetter>(Object,Function);
    // TWidgetAttribute Att(Getter);
    TWidgetAttribute(std::shared_ptr<TWidgetAttributeValueGetterBase> InGetter)
        :Getter(InGetter)
    {}


    TWidgetAttribute(const TWidgetAttribute& Other)
    {
        Getter=Other.Getter;
    }

    TWidgetAttribute(TWidgetAttribute&& Other)
    {
        Getter=std::move(Other.Getter);
    }


    TWidgetAttribute& operator =(const T& InValue)
    {
        Getter=std::make_shared<TWidgetAttributeConstantValueGetter<T>>(InValue);
        return *this;
    }

    TWidgetAttribute& operator =(T&& InValue)
    {
        Getter=std::make_shared<TWidgetAttributeConstantValueGetter<T>>(std::forward<T>(InValue));
        return *this;
    }

    TWidgetAttribute& operator =(std::function<T()> InFunction)
    {
        TFunction<T> * RawFunction = new SSTDFunction<T>(InFunction);

        Getter=std::make_shared<TWidgetAttributeFunctionValueGetter<T>>(
            std::shared_ptr<TFunction<T>>(RawFunction)
        );

        return *this;
    }

    TWidgetAttribute& operator =(std::shared_ptr<TWidgetAttributeValueGetterBase> InGetter)
    {
        Getter=InGetter;
        return *this;
    }

    TWidgetAttribute& operator =(const TWidgetAttribute& Other)
    {
        if (&Other == this)
        {
            return  *this;
        }

        Getter=Other.Getter;

        return *this;
    }

    TWidgetAttribute& operator =(TWidgetAttribute&& Other)
    {
        Getter = std::move(Other.Getter);
        return *this;
    }



    bool IsValid() const
    {
        if(!Getter)
        {
            return false;
        }

        return Getter->IsValid();
    }

    T Get() const
    {
        if (Getter && Getter->IsValid())
        {
            T Value;
            Getter->GetValuePointer(&Value);
            return Value;
        }

        return T();
    }

    void Reset()
    {
        Getter.reset();
    }

};


